// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#pragma warning(disable:4201) // glm: nonstandard extension used: nameless struct/union
#include "glm/glm.hpp"

#include "WorldEntityProperty_pugi.h"

#include <atomic>
#include <map>
#include <string>
#include <vector>

class FWorldEntityPart; // Owner if the property is not an orphan.

/**
 * No reflection in C++. We need a custom property class for notifications.
 */
class FWorldEntityPropertyBase
{
public:

	FWorldEntityPropertyBase(FWorldEntityPart* part);
	~FWorldEntityPropertyBase();

protected:

	void PropertyChanged();
	void PartAdded(FWorldEntityPart* child);

private:

	FWorldEntityPart*	_part;

};

/**
 * Typed property as the underlying storage. Just behave like T.
 */
template<typename T>
class FWorldEntityPropertyTyped : public FWorldEntityPropertyBase
{
public:
	typedef FWorldEntityPropertyBase Super;
	typedef T ValueType;

	FWorldEntityPropertyTyped(FWorldEntityPart* part, T&& value = T())
		: Super(part)
		, _value(std::move(value))
	{}

	T& Get()
	{
		return _value;
	}

	const T& Get() const
	{
		return _value;
	}

	operator T&()
	{
		// Implicit conversion to T.
		return _value;
	}

	operator const T&() const
	{
		// Implicit conversion to T.
		return _value;
	}

	void Set(const T& rhs)
	{
		this->operator=(rhs);
	}

	void Set(T&& rhs)
	{
		this->operator=(std::move(rhs));
	}

	FWorldEntityPropertyTyped<T>& operator=(const T& rhs)
	{
		// Copy assignment.
		_value = rhs;
		PropertyChanged();
		return *this;
	}

	FWorldEntityPropertyTyped<T>& operator=(T&& rhs)
	{
		// Move assignment.
		_value = std::move(rhs);
		PropertyChanged();
		return *this;
	}

	bool operator==(const T& rhs) const
	{
		// Equality.
		return static_cast<T>(*this) == rhs;
	}

protected:

	T	_value;

};

/**
 * Property for simple types such as integer, string, ..
 */
template<typename T>
class FWorldEntityProperty : public FWorldEntityPropertyTyped<T>
{
public:
	typedef FWorldEntityPropertyTyped<T> Super;

	FWorldEntityProperty(FWorldEntityPart* part, T&& value = T())
		: Super(part, std::move(value))
	{}

	FWorldEntityProperty(FWorldEntityPart* part, T&& value, const json& j, const char* f)
		: Super(part, f ? std::move(j.value<T>(f, value)) : std::move(value))
	{}

	FWorldEntityProperty(FWorldEntityPart* part, T&& value, const pugi::xml_node& elem, const char* at)
		: Super(part, at ? std::move(pugi::get_as<T>(elem, at, std::move(value))) : std::move(value))
	{}

	FWorldEntityProperty<T>& operator=(const T& rhs)
	{
		// Copy assignment.
		static_cast<Super*>(this)->operator=(rhs);
		return *this;
	}

	FWorldEntityProperty<T>& operator=(T&& rhs)
	{
		// Move assignment.
		static_cast<Super*>(this)->operator=(std::move(rhs));
		return *this;
	}

	bool operator==(const T& rhs) const
	{
		// Equality.
		return static_cast<const Super*>(this)->operator==(rhs);
	}
};

/**
 * Property for booleans..
 */
struct FWorldEntityBoolTrueFalseRule
{
	static const char* True() { return "true"; }
	static const char* False() { return "false"; }
	static bool IsTrue(const std::string& str) { return str == True(); }
};
struct FWorldEntityBoolYesNoRule
{
	static const char* True() { return "yes"; }
	static const char* False() { return "no"; }
	static bool IsTrue(const std::string& str) { return str == True(); }
};
template<typename Rule>
class FWorldEntityBoolProperty : public FWorldEntityPropertyTyped<std::string>
{
public:
	typedef FWorldEntityPropertyTyped<std::string> Super;

	FWorldEntityBoolProperty(FWorldEntityPart* part, std::string&& value = Rule::False())
		: Super(part, std::move(value))
	{}

	FWorldEntityBoolProperty(FWorldEntityPart* part, std::string&& value, const pugi::xml_node& elem, const char* at)
		: Super(part, at ? std::move(pugi::get_as<std::string>(elem, at, std::move(value))) : std::move(value))
	{}

	FWorldEntityBoolProperty& operator=(bool rhs)
	{
		// Assignment.
		static_cast<Super*>(this)->operator=(rhs ? Rule::True() : Rule::False());
		return *this;
	}

	explicit operator bool() const
	{
		return Rule::IsTrue(Get());
	}
};

/**
 * Property for speeds..
 */
class FWorldEntitySpeedProperty : public FWorldEntityPropertyTyped<std::string>
{
public:
	typedef FWorldEntityPropertyTyped<std::string> Super;

	enum NolimitTag { Nolimit };
	enum UndefinedTag { Undefined };

	FWorldEntitySpeedProperty(FWorldEntityPart* part, std::string&& value = "no limit")
		: Super(part, std::move(value))
	{}

	FWorldEntitySpeedProperty(FWorldEntityPart* part, std::string&& value, const pugi::xml_node& elem, const char* at)
		: Super(part, at ? std::move(pugi::get_as<std::string>(elem, at, std::move(value))) : std::move(value))
	{}

	FWorldEntitySpeedProperty& operator=(double rhs)
	{
		// Assignment.
		static_cast<Super*>(this)->operator=(std::to_string(rhs));
		return *this;
	}

	FWorldEntitySpeedProperty& operator=(NolimitTag)
	{
		// Assignment.
		static_cast<Super*>(this)->operator=("no limit");
		return *this;
	}

	FWorldEntitySpeedProperty& operator=(UndefinedTag)
	{
		// Assignment.
		static_cast<Super*>(this)->operator=("undefined");
		return *this;
	}

	bool IsNolimit() const
	{
		return *static_cast<const Super*>(this) == "no limit";
	}

	bool IsUndefined() const
	{
		return *static_cast<const Super*>(this) == "undefined";
	}
};

/**
 * Property for array types.
 */
template<typename T>
class FWorldEntityArrayProperty : public FWorldEntityPropertyTyped<std::vector<T>>
{
public:
	typedef FWorldEntityPropertyTyped<std::vector<T>> Super;
	typedef typename Super::ValueType ValueType;

	using FWorldEntityPropertyTyped<ValueType>::_value;

	FWorldEntityArrayProperty(FWorldEntityPart* part, ValueType&& value = ValueType())
		: Super(part, std::move(value))
	{}

	FWorldEntityArrayProperty(FWorldEntityPart* part, const json& j, const char* f)
		: Super(part)
	{
		if (j.is_object() && f)
		{
			// If field "f" is missing in json "j", init as an empty array.
			// If field "f" is malformed, throw an exception.
			auto data = j.value<ValueType>(f, ValueType());
			_value = std::move(data);
		}
		else
		{
			_value = ValueType();
		}
	}

	FWorldEntityArrayProperty<T>& operator=(const ValueType& rhs)
	{
		// Copy assignment.
		static_cast<Super*>(this)->operator=(rhs);
		return *this;
	}

	FWorldEntityArrayProperty<T>& operator=(ValueType&& rhs)
	{
		// Move assignment.
		static_cast<Super*>(this)->operator=(std::move(rhs));
		return *this;
	}

	typename ValueType::const_iterator begin() const
	{
		// Range const iterator.
		return _value.begin();
	}

	typename ValueType::const_iterator end() const
	{
		// Range const iterator.
		return _value.end();
	}

	typename ValueType::size_type size() const
	{
		// Size
		return _value.size();
	}

	typename ValueType::const_reference operator[](typename ValueType::size_type pos) const
	{
		// operator[]
		return _value[pos];
	}

};

/**
 * Property for map types.
 */
template<typename T>
class FWorldEntityMapProperty : public FWorldEntityPropertyTyped<std::map<std::string,T>>
{
public:
	typedef FWorldEntityPropertyTyped<std::map<std::string,T>> Super;
	typedef typename Super::ValueType ValueType;

	using FWorldEntityPropertyTyped<ValueType>::_value;

	FWorldEntityMapProperty(FWorldEntityPart* part, const json& j = json(), const char* f = nullptr)
		: Super(part)
	{
		if (j.is_object() && f)
		{
			// If field "f" is missing in json "j", init as an empty map.
			// If field "f" is malformed, throw an exception.
			ValueType data = ValueType();
			auto it = j.find(f);
			if (it != j.end())
			{
				for (const auto& p : (*it).items())
				{
					data[p.key()] = p.value().get<T>();
				}
			}
			_value = std::move(data);
		}
		else
		{
			_value = ValueType();
		}
	}

	FWorldEntityMapProperty<T>& operator=(const ValueType& rhs)
	{
		// Copy assignment.
		static_cast<Super*>(this)->operator=(rhs);
		return *this;
	}

	FWorldEntityMapProperty<T>& operator=(ValueType&& rhs)
	{
		// Move assignment.
		static_cast<Super*>(this)->operator=(std::move(rhs));
		return *this;
	}

	typename ValueType::const_iterator begin() const
	{
		// Range const iterator.
		return _value.begin();
	}

	typename ValueType::const_iterator end() const
	{
		// Range const iterator.
		return _value.end();
	}

	typename ValueType::size_type size() const
	{
		// Size
		return _value.size();
	}

	typename ValueType::mapped_type& operator[](const typename ValueType::key_type& key) const
	{
		// operator[]
		return _value[key];
	}

};

/**
 * Typed property for entity/part-entity reference such as foreign keys.
 */
template<typename T>
class FWorldEntityReference : public FWorldEntityProperty<std::int64_t>
{
public:
	typedef FWorldEntityProperty<std::int64_t> Super;

	FWorldEntityReference(FWorldEntityPart* part, const json& j = json(), const char* f = nullptr)
		: Super(part, -1, j, f)
	{}

	FWorldEntityReference(FWorldEntityPart* part, const pugi::xml_node& elem, const char* at)
		: Super(part, -1, elem, at)
	{}

	std::shared_ptr<T> operator*()
	{
		// Dereference.
		return _ref.lock();
	}

	FWorldEntityReference<T>& operator=(const std::shared_ptr<T>& rhs)
	{
		_ref = rhs;
		static_cast<Super*>(this)->operator=(rhs ? rhs->ID.Get() : -1);
		return *this;
	}

	FWorldEntityReference<T>& operator=(std::int64_t id)
	{
		static_cast<Super*>(this)->operator=(id);
		return *this;
	}

	void Resolve(const std::shared_ptr<T>& refee)
	{
		_ref = refee;
	}

private:

	std::weak_ptr<T>	_ref;

};

/**
 * Typed property for entity/part-entity reference such as foreign keys. (one to many ver)
 */
template<typename T>
class FWorldEntityArrayReference : public FWorldEntityArrayProperty<std::int64_t>
{
public:
	typedef FWorldEntityArrayProperty<std::int64_t> Super;

	class WeakItem
	{
		std::weak_ptr<T>	_ref;

	public:
		WeakItem(const std::shared_ptr<T>& ref = nullptr)
			: _ref(ref)
		{}

		std::shared_ptr<T> operator*() const
		{
			// Dereference.
			return _ref.lock();
		}
	};
	typedef std::vector<WeakItem> WeakItemArrayType;

	FWorldEntityArrayReference(FWorldEntityPart* part, const json& j = json(), const char* f = nullptr)
		: Super(part, j, f)
	{
		_refs.resize(_value.size());
	}

	FWorldEntityArrayReference<T>& operator=(const std::vector<std::shared_ptr<T>>& rhs)
	{
		// Copy assignment.
		std::vector<std::int64_t> ids;
		std::vector<WeakItem> refs;
		for (const auto& p : rhs)
		{
			ids.push_back(p ? p->ID.Get() : -1);
			refs.emplace_back(p);
		}

		_refs = std::move(refs);
		static_cast<Super*>(this)->operator=(ids);
		return *this;
	}

	void Resolve(const std::vector<std::shared_ptr<T>>& refee)
	{
		_refs.clear();
		for (const auto& p : refee)
		{
			_refs.emplace_back(p);
		}
	}

	typename WeakItemArrayType::const_iterator begin() const
	{
		// Range const iterator.
		return _refs.begin();
	}

	typename WeakItemArrayType::const_iterator end() const
	{
		// Range const iterator.
		return _refs.end();
	}

	typename WeakItemArrayType::size_type size() const
	{
		// Size
		return _refs.size();
	}

	typename WeakItemArrayType::const_reference operator[](typename WeakItemArrayType::size_type pos) const
	{
		// operator[]
		return _refs[pos];
	}

private:

	WeakItemArrayType	_refs;

};

/**
 * Typed property for entity/part-part reference.
 */
template<typename T, int MinOccurance>
class FWorldEntityPartReference : public FWorldEntityProperty<typename T::Ptr>
{
public:
	typedef FWorldEntityProperty<typename T::Ptr> Super;

	using Super::_value;

	FWorldEntityPartReference(FWorldEntityPart* part, typename T::Ptr&& value = nullptr)
		: Super(part, std::move(value))
	{}

	FWorldEntityPartReference(FWorldEntityPart* part, const pugi::xml_node& elem, const char* name)
		: Super(part, CreateAny(part, elem, name))
	{}

	T& operator*()
	{
		// Deference operator.
		return *_value;
	}

	T* operator->()
	{
		// Arrow operator.
		return _value.get();
	}

	explicit operator bool() const
	{
		return static_cast<bool>(_value);
	}

	FWorldEntityPartReference<T, MinOccurance>& operator=(const std::shared_ptr<T>& rhs)
	{
		PartAdded(rhs.get());
		static_cast<Super*>(this)->operator=(rhs);
		return *this;
	}

	FWorldEntityPartReference<T, MinOccurance>& operator=(std::shared_ptr<T>&& rhs)
	{
		PartAdded(rhs.get());
		static_cast<Super*>(this)->operator=(std::move(rhs));
		return *this;
	}

private:
	static typename T::Ptr CreateAny(FWorldEntityPart* part, const pugi::xml_node& elem, const char* name)
	{
		pugi::xml_node match = elem.child(name);
		if (match)
		{
			return std::make_shared<T>(part, match);
		}
		return nullptr;
	}

};

/**
 * Typed property for entity/part-part reference. (one to many ver)
 */
template<typename T, int MinOccurance, int MaxOccurance>
class FWorldEntityPartArrayReference : public FWorldEntityArrayProperty<typename T::Ptr>
{
public:
	typedef FWorldEntityArrayProperty<typename T::Ptr> Super;
	typedef typename Super::ValueType ValueType;

	FWorldEntityPartArrayReference(FWorldEntityPart* part)
		: Super(part)
	{}

	FWorldEntityPartArrayReference(FWorldEntityPart* part, const pugi::xml_node& elem, const char* name)
		: Super(part, CreateMulti(part, elem, name))
	{}

	FWorldEntityPartArrayReference<T, MinOccurance, MaxOccurance>& operator=(const ValueType& rhs)
	{
		for (auto& part : rhs) PartAdded(part.get());
		static_cast<Super*>(this)->operator=(rhs);
		return *this;
	}

	FWorldEntityPartArrayReference<T, MinOccurance, MaxOccurance>& operator=(ValueType&& rhs)
	{
		for (auto& part : rhs) PartAdded(part.get());
		static_cast<Super*>(this)->operator=(std::move(rhs));
		return *this;
	}

private:
	static ValueType CreateMulti(FWorldEntityPart* part, const pugi::xml_node& elem, const char* name)
	{
		ValueType cppobjs;
		for (const auto& elemchild : elem.children(name))
		{
			cppobjs.push_back(std::make_shared<T>(part, elemchild));
		}
		return cppobjs;
	}

};
